<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Tokenize</title>
  </head>
  <body>
    <script>
      let regexp = /([0-9\.]+)|([ ]+)|([\r\n]+)|(\+)|(\-)|(\*)|(\/)/g;
      let dictionary = [
        "Number",
        "Whitespace",
        "LineTerminator",
        "+",
        "-",
        "*",
        "/",
      ];
      function* tokenize(source) {
        let result = null;
        let lastIndex = 0;

        do {
          lastIndex = regexp.lastIndex;
          result = regexp.exec(source);
          if (!result) break;

          if (regexp.lastIndex - lastIndex > result[0].length) {
            throw new Error(
              'Unexpected token "' +
                source.slice(lastIndex, regexp.lastIndex - result[0].length) +
                '"!'
            );
          }
          let token = {
            type: null,
            value: null,
          };
          for (let index = 0; index < dictionary.length; index++) {
            if (result[index + 1]) {
              token.type = dictionary[index];
            }
          }
          token.value = result[0];
          yield token;
        } while (result);
        yield { type: "EOF" };
      }
      function additiveExpression(source) {
        if (source[0].type === "Number") {
          multiplicativeExpression(source);
          return additiveExpression(source);
        }
        if (source[0].type === "MultiplicativeExpression") {
          let node = {
            type: "AdditiveExpression",
            children: [source.shift()],
          };
          source.unshift(node);
          return additiveExpression(source);
        }
        if (
          source[0].type === "AdditiveExpression" &&
          source.length > 1 &&
          (source[1].type === "+" || source[1].type === "-")
        ) {
          let node = {
            type: "AdditiveExpression",
            children: [source.shift(), source.shift()],
          };
          multiplicativeExpression(source);
          node.children.push(source.shift());
          source.unshift(node);
          return additiveExpression(source);
        }
        if (source[0].type === "AdditiveExpression") {
          return source[0];
        }
        throw new Error('Unexpected type "' + source[0].type + '"!');
      }
      function multiplicativeExpression(source) {
        if (source[0].type === "Number") {
          let node = {
            type: "MultiplicativeExpression",
            children: source.shift(),
          };
          source.unshift(node);
          return multiplicativeExpression(source);
        }
        if (
          source[0].type === "MultiplicativeExpression" &&
          source.length > 1 &&
          (source[1].type === "*" || source[1].type === "/")
        ) {
          let node = {
            type: "MultiplicativeExpression",
            children: [source.shift(), source.shift(), source.shift()],
          };
          console.log(node);
          source.unshift(node);
          return multiplicativeExpression(source);
        }
        if (source[0].type === "MultiplicativeExpression") {
          return source[0];
        }
        throw new Error('Unexpected type "' + source[0].type + '"!');
      }
      function expression(source) {
        if (
          source[0].type === "AdditiveExpression" &&
          source[1].type === "EOF"
        ) {
          let node = {
            type: "Expression",
            children: [source.shift(), source.shift()],
          };
          source.unshift(node);
          return node;
        }
        additiveExpression(source);
        return expression(source);
      }
      function start() {
        let source = [];
        for (const token of tokenize("1024 * 10 + 13")) {
          if (token.type === "Whitespace" || token.type === "LineTerminator") {
            continue;
          }
          source.push(token);
        }
        console.log(expression(source));
      }
      start();
    </script>
  </body>
</html>
